// scaup - Scala up!
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id$
package locutor.model.parsing.grgen

import java.io.File
import java.text.MessageFormat.{format}

import scaup.Views._

/** This object provides a simple interface to the GRG parser class.
 *
 * @thanks GrGEN.NET manual and GrGen.g for syntax rules (v2.5.x)
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
object GRG extends GRGParser {
  def apply(input: File): R =
    if(input == null || !(input exists))
      throw new Exception(format("Physical GRG file may not be null (''{0}'').", input))
    else apply(input.content)
  
  def apply(input: String) =
    if(input == null)
      throw new Exception("Physical GRG representation may not be null.")
    else parse(input)
  
  def parse(input: String) = phrase(grg)(new lexical.Scanner(input)) match {
    case Success(result, _) => result
    case n@_                => throw new Exception(format("GRG parsing error in ''{0} ...}''\n", input.lines.next) + n)
  }
}

/** A GrGEN rewrite system (.grg) parser.
 *
 * At the moment only primitive string representations of GrGEN rewrite systems are supported.
 * No need for sophisticated AST yet due to direct looping into GrGEN.
 * 
 * Restrictions:
 * - HEX numbers
 * - ``#include''
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
private [grgen] class GRGParser extends XGRSParser with ExprParser {
  /* Fill in abstract defs*/
  type Tokens = GRGLexer
  val lexical: Tokens = new GRGLexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List(
    "alternative",
    "delete", "dpo", 
    "emit", "emitpre", "emitpost", "eval", "exact", "exec",
    "hom",
    "if", "independent", "induced", "iterated",
    "map", "modify", "multiple",
    "negative",
    "optional", 
    "pattern", "patternpath",
    "replace", "return", "rule",
    "set", 
    "test",
    "using",
    "var"
    // Expression parser reserved:
    // "int", "double", "float", "string",
    // "true", "false", "null",
    // "visited", "nameof", "typeof"
    // XGRS parser reserver:
    // "def", "true", "false"
  ) ++ XGRSParser.reserved ++ ExprParser.reserved

  /* @todo '?--?', '+=', '<-->', '<--', '-->', '++', '--' */  
  lexical.delimiters ++= List(
    "{", "}", "<-", "->", "?-", "-?", ";", "\\"
    // Expression parser delimiters:
    // "[", "]", "(", ")",
    // "!", "?", ":", ",", ".", "::", 
    // "^", "&&", "||", "&", "|",
    // "<<", ">>>", ">>", "+", "-", "*", "/", "%", "~",
    // "<=", "<", "==", "!=", ">=", ">"
    // XGRS parser delimiters:
    //  "!", "*", "+", "[", "]", ":", "$", "&&", "&", "||", "|", "<", ">", "(", ")", ",", "=", "%", "?", "^"
  ) ++ XGRSParser.delimiters ++ ExprParser.delimiters  
  
  /* Define the grammar */
  def grg = opt(using) ~ ((rules)*) ^^ {
    case Some(using) ~ rules => R(using, rules)
    case None        ~ rules => R(Nil, rules)
  }
  
  def using = ("using" ~> ident) ~ (("," ~> ident)*) <~ ";" ^^ {
    case i ~ is => i :: is
  }
  
  def rules = opt("exact" | "induced" | "dpo") ~ (test_stmt | rule_stmt | pattern_stmt) ^^ {
    case m ~ r => (m match {case Some(m) => m + " " case _ => ""}) + r
  }  
  
  def test_stmt = ("test" ~> ident) ~ opt(params) ~ opt(return_types) ~ ("{" ~> ((pattern)*) <~ "}") ^^ {
    case id ~ params ~ rts ~ ps => "test " +  id + ∑(params) + ∑(rts) + "{" + ∑(ps) + "}"
  }

  def rule_stmt = ("rule" ~> ident) ~ opt(params) ~ opt(return_types) ~ ("{" ~> ((pattern)*)) ~ ((replace | modify) <~ "}") ^^ {
    case id ~ params ~ rts ~ ps ~ rm => "rule " + id + ∑(params) + ∑(rts) + "{" + ∑(ps) + rm + "}"
  }

  def pattern_stmt =
    (("pattern" ~> ident) ~ opt(params) ~ opt(return_types) ~ ("{" ~> ((pattern_body)*)) ~ (opt(replace | modify) <~ "}") ^^ {
      case id ~ params ~ rts ~ ps ~ rm => "pattern " + id + ∑(params) + ∑(rts) + "{" + ∑(ps) + ∑(rm) + "}"}
    | pattern_body
    )

  def pattern =
    ( ("pattern" ~ "{") ~> (((pattern_body)*) <~ "}") ^^ {case ps => "pattern{" + ∑(ps) + "}"}
    | pattern_body
    )

  def pattern_body: Parser[String] = graphlet | alternative | iterated | negative | independent | condition | return_stmt | hom | exact | induced

  def graphlet =
    ( (node ~ opt(continuation)) ^^ {case n ~ c => n + ∑(c) + ";"}
    | continuation               ^^ {case c      => c + ";"}
    ) <~ ";"

  def alternative = "alternative" ~ "{" ~ ((alternative_body)+) ~ "}" ^^ {case abs => "alternative{" + abs + "}"}

  def alternative_body = (ident <~ "{") ~ ((pattern_body)*) ~ (opt(replace | modify) <~ "}") ^^ {
    case id ~ ps ~ rm => id+"{" + ∑(ps) + ∑(rm) + "}"
  }

  def iterated = ("iterated" | "optional" | "multiple") ~ (opt(ident) <~ "{") ~ ((pattern_body)*) ~ (opt(replace | modify) <~ "}") ^^ {
    case m ~ id ~ ps ~ rm => m+" " + ∑(id) + "{" + ∑(ps) + ∑(rm) + "}"
  }

  //def negative = ("negative" ~ "{") ~> ((("patternpath" | "pattern") ~ ";")*) ~ (((pattern_body)*) <~ "}")
  def negative = ("negative" ~ "{") ~> (((pattern_body)*) <~ "}") ^^ {
    case pb => "negative{" + ∑(pb) + "}"
  }

  //def independent = "independent" ~ "{" ~ ((("patternpath" | "pattern") ~ ";")*) ~ ((pattern_body)*) ~ "}"
  def independent = ("independent" ~ "{") ~> (((pattern_body)*) <~ "}") ^^ {
    case ps => "independent{" + ∑(ps) + "}"
  }

  def condition = ("if" ~ "{") ~> (((expr <~ ";")*) <~ "}") ^^ {
    case exprs => "if{" + ((exprs map (_ + ";")) mkString) + "}"
  }

  def hom = ("hom" ~ "(") ~> (ident ~ ((("," ~> ident)*) <~ ")")) <~ ";" ^^ {
    case id ~ ids => (id :: ids) mkString ("hom(", ",", ");")
  }

  def exact = ("exact" ~ "(") ~> (ident ~ ((("," ~> ident)*) <~ ")")) <~ ";"  ^^ {
    case id ~ ids => (id :: ids) mkString ("exact(", ",", ");")
  }

  def induced = ("induced" ~ "(") ~> (ident ~ ((("," ~> ident)*) <~ ")")) <~ ";" ^^ {
    case id ~ ids => (id :: ids) mkString ("induced(", ",", ");")
  }

  def replace = ("replace" ~> opt(ident)) ~ opt(params) ~ ("{" ~> ((replace_body)*) <~ "}") ^^ {
    case id ~ ps ~ rbs => "replace " + ∑(id) + ∑(ps) + "{" + ∑(rbs) + "}"
  }

  def replace_body =  (graphlet | eval | return_stmt | exec | emit)

  def modify = ("modify" ~> opt(ident)) ~ opt(params) ~ ("{" ~> ((modify_body)*) <~ "}") ^^ {
    case id ~ ps ~ mbs => "modify " + ∑(id) + ∑(ps) + "{" + ∑(mbs) + "}"
  }

  def modify_body = (graphlet | eval | delete | return_stmt | exec | emit)

  def node =
    ( (opt(annotated_ident | annotation) <~ ":") ~ ( (ident ~ args) ^^ {case id ~ as => id+as}
                                                   | (ident | typeOf) ~ opt(type_constraint) ~ opt("<" ~> ident <~ ">") ^^ {
                                                     case id ~ tc ~ rt => id+ ∑(tc) + (rt match {case Some(rt) => "<"+rt+">" case _=> ""})
                                                   }
                                                   )                ^^ {case id ~ t  => ∑(id) + ":" + t}
    | ident ~ opt(args)                                             ^^ {case id ~ as => id + ∑(as)}
    | ("." ~> opt(annotation))                                      ^^ {case as      => "." + ∑(as)}
    )

  def edge = 
    ( ("<-" | "-") ~ opt(edge_refinement) ~ ("->" | "-") ^^ {case l ~ e ~ r => l + ∑(e) + r}
    | ("?-" ~ opt(edge_refinement) ~ "-?")               ^^ {case l ~ e ~ r => l + ∑(e) + r}
    )

  def edge_refinement = 
    ( (opt(annotated_ident) <~ ":") ~ (ident | typeOf) ~ opt(type_constraint) ~ opt("<" ~> ident <~ ">") ^^ {
      case v ~ t ~ tc ~ rt => ∑(v) + ":" + t + ∑(tc) + (rt match {case Some(rt) => "<"+rt+">" case _=> ""})
    }
    | ident
    )

  def continuation: Parser[String] = edge ~ opt(node ~ opt(continuation)) ^^ {
    case e ~ Some(n ~ Some(c)) => e+n+c
    case e ~ Some(n ~ None)    => e+n
    case e ~ None              => e
  }

  def return_stmt = ("return" ~ "(") ~> (expr ~ (("," ~> expr)*)) <~ (")" ~ ";") ^^ {
    case expr ~ exprs => (expr :: exprs) mkString ("return(", ",", ");")
  }

  def params = "(" ~> opt(param ~ (("," ~> param)*)) <~ ")" ^^ {
    case Some(p ~ ps) => (p :: ps) mkString ("(", "," , ")")
    case None         => "()"
  }

  def param = (edge | variable | node)

  def args = ("(" ~> opt(expr ~ (("," ~> expr)*))) <~ ")" ^^ {
    case Some(expr ~ exprs) => (expr :: exprs) mkString ("(", ",", ")")
    case None               => "()"
  }

  def type_constraint =
    ( "\\" ~> ident                              ^^ {case id  => "\\"+id}
    | "\\" ~> ("(" ~> repsep(ident, "+") <~ ")") ^^ {case ids => ids mkString ("\\(", "+", ")")}
    )

  def return_types = (":" ~ "(") ~> (opt((ident | primitives) ~ ((("," ~> (ident | primitives))*))) <~ ")") ^^ {
    case Some(t ~ ts) => (t :: ts) mkString (":(", ",", ")")
    case None         => ":()"
  }

  def variable = "var" ~> (ident <~ ":") ~
    ( ident | primitives
    | ("map" ~ "<") ~> (ident <~ ",") ~ (ident <~ ">") ^^ {case f ~ s => "map<"+f+","+"s"+">"}
    | ("set" ~ "<") ~> (ident <~ ">")                  ^^ {case f     => "set<"+f+">"}
    ) ^^ {case id ~ t => "var "+id+":"+t}
               
  def delete = ("delete" ~ "(") ~> (ident ~ (("," ~> ident)*)) <~ (")" ~ ";") ^^ {
    case id ~ ids => "delete("+((id :: ids) mkString ",")+");"
  }

  def exec = ("exec" ~ "(") ~> xgrs <~ (")" ~ ";") ^^ {
    case xgrs => "exec("+xgrs+");"
  }

  def emit = ("emit" | "emitpre" | "emitpost") ~ ("(" ~> expr) ~ (("," ~> expr)*) <~ (")" ~ ";") ^^ {
    case e ~ expr ~ exprs => e+"("+((expr :: exprs) mkString ",")+");"
  }

  def eval = ("eval" ~ "{") ~> (((assignment <~ ";")*) <~ "}") ^^ {
    case as => "eval{"+((as map (_ + ";")) mkString)+"}"
  }

  def assignment = (((ident <~ ".") ~ ident) ^^ {case c ~ m => c+"."+m} | visited) ~ ("=" ~> expr) ^^ {
    case l ~ r => l+"="+r
  }

}

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
private [grgen] class GRGLexer extends StdLexical with RegexParsers {
  override type Elem = Char
  
  override def token = 
    ( string ^^ StringLit
    | number ^^ NumericLit
    | delim
    | EofCh ^^^ EOF
    | id     ^^ {case i if reserved contains i => Keyword(i) case i => Identifier(i)}        
    | '\"' ~> failure("Unterminated string")
    | failure("Illegal character")
    )
  
  def string  =      
    ( '\"' ~> rep(charSeq | chrExcept('\"', '\n', EofCh)) <~ '\"' ^^ { _ mkString "" }
    | '\'' ~> rep(charSeq | chrExcept('\'', '\n', EofCh)) <~ '\'' ^^ { _ mkString "" }    
    )
  
  def number  = """-?(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r
  
  def id      = rep(letter | digit | elem("underscore", _=='_')) ^^ { _ mkString "" }
  
  def charSeq = 
    ( '\\' ~ '\"' ^^^ "\"" | '\\' ~ '\\' ^^^ "\\" | '\\' ~ '/'  ^^^ "/" | '\\' ~ 'b'  ^^^ "\b" | '\\' ~ 'f'  ^^^ "\f" 
    | '\\' ~ 'n'  ^^^ "\n" |'\\' ~ 'r'  ^^^ "\r" |'\\' ~ 't'  ^^^ "\t" | '\\' ~ 'u' ~> unicodeBlock
    )
  
  import scala.io.UTF8Codec.{encode}
  def unicodeBlock = hexDigit ~ hexDigit ~ hexDigit ~ hexDigit ^^ {
    case a ~ b ~ c ~ d => new String(encode(Integer.parseInt(List(a, b, c, d) mkString "", 16)))
  }
  
  val hexDigits = Set[Char]() ++ "0123456789abcdefABCDEF".toArray
  
  def hexDigit = elem("hex digit", hexDigits.contains(_))
}
